/**
 *
 */
package com.browseengine.bobo.search.section;

import java.io.IOException;
import java.util.ArrayList;

import org.apache.lucene.index.AtomicReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;

/**
 *
 */
public class SectionSearchQueryPlanBuilder {
  public static class TranslationException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    public TranslationException(String message) {
      super(message);
    }
  }

  protected final AtomicReader _reader;
  protected final MetaDataCacheProvider _cacheProvider;

  public SectionSearchQueryPlanBuilder(AtomicReader reader) {
    _reader = reader;
    _cacheProvider = (reader instanceof MetaDataCacheProvider ? (MetaDataCacheProvider) reader
        : null);
  }

  /**
   * Gets a query plan for the given query.
   * It is assumed that <code>query</code> is already rewritten before this call.
   * @param query
   * @return SectionSearchQueryPlan
   * @throws IOException
   */
  public SectionSearchQueryPlan getPlan(Query query) throws IOException {
    if (query != null) {
      SectionSearchQueryPlan textSearchPlan = translate(query);

      if (!(textSearchPlan instanceof UnaryNotNode)) {
        return textSearchPlan;
      }
    }
    return null;
  }

  /**
   * Translates a Lucence Query object to an SectionSearchQueryPlan
   * @param query
   * @param reader
   * @return
   * @throws IOException
   */
  private SectionSearchQueryPlan translate(Query query) throws IOException {
    if (query != null) {
      if (query instanceof TermQuery) {
        return translateTermQuery((TermQuery) query);
      } else if (query instanceof PhraseQuery) {
        return translatePhraseQuery((PhraseQuery) query);
      } else if (query instanceof BooleanQuery) {
        return translateBooleanQuery((BooleanQuery) query);
      } else if (query instanceof MetaDataQuery) {
        MetaDataQuery mquery = (MetaDataQuery) query;
        MetaDataCache cache = (_cacheProvider != null ? _cacheProvider.get(mquery.getTerm()) : null);

        if (cache != null) {
          return ((MetaDataQuery) query).getPlan(cache);
        } else {
          return ((MetaDataQuery) query).getPlan(_reader);
        }
      } else {
        throw new TranslationException("unable to translate Query class: "
            + query.getClass().getName());
      }
    }
    return null;
  }

  private SectionSearchQueryPlan translateTermQuery(TermQuery query) throws IOException {
    return new TermNode(query.getTerm(), _reader);
  }

  private SectionSearchQueryPlan translatePhraseQuery(PhraseQuery query) throws IOException {
    Term[] terms = query.getTerms();
    TermNode[] nodes = new TermNode[terms.length];
    int[] positions = query.getPositions();
    for (int i = 0; i < terms.length; i++) {
      nodes[i] = new TermNode(terms[i], positions[i], _reader);
    }
    return new PhraseNode(nodes, _reader);
  }

  private SectionSearchQueryPlan translateBooleanQuery(BooleanQuery query) throws IOException {
    ArrayList<Query> requiredClauses = new ArrayList<Query>();
    ArrayList<Query> prohibitedClauses = new ArrayList<Query>();
    ArrayList<Query> optionalClauses = new ArrayList<Query>();
    BooleanClause[] clauses = query.getClauses();
    for (BooleanClause clause : clauses) {
      if (clause.isRequired()) {
        requiredClauses.add(clause.getQuery());
      } else if (clause.isProhibited()) {
        prohibitedClauses.add(clause.getQuery());
      } else {
        optionalClauses.add(clause.getQuery());
      }
    }

    SectionSearchQueryPlan positiveNode = null;
    SectionSearchQueryPlan negativeNode = null;

    if (requiredClauses.size() > 0) {
      if (requiredClauses.size() == 1) {
        positiveNode = translate(requiredClauses.get(0));
      } else {
        SectionSearchQueryPlan[] subqueries = translate(requiredClauses);
        if (subqueries != null && subqueries.length > 0) positiveNode = new AndNode(subqueries);
      }
    } else if (optionalClauses.size() > 0) {
      if (optionalClauses.size() == 1) {
        positiveNode = translate(optionalClauses.get(0));
      } else {
        SectionSearchQueryPlan[] subqueries = translate(optionalClauses);
        if (subqueries != null && subqueries.length > 0) positiveNode = new OrNode(subqueries);
      }
    }

    if (prohibitedClauses.size() > 0) {
      if (prohibitedClauses.size() == 1) {
        negativeNode = translate(prohibitedClauses.get(0));
      } else {
        negativeNode = new OrNode(translate(prohibitedClauses));
      }
    }

    if (negativeNode == null) {
      return positiveNode;
    } else {
      if (positiveNode == null) {
        return new UnaryNotNode(negativeNode);
      } else {
        return new AndNotNode(positiveNode, negativeNode);
      }
    }
  }

  private SectionSearchQueryPlan[] translate(ArrayList<Query> queries) throws IOException {
    int size = queries.size();
    ArrayList<SectionSearchQueryPlan> result = new ArrayList<SectionSearchQueryPlan>(size);
    for (int i = 0; i < size; i++) {
      SectionSearchQueryPlan plan = translate(queries.get(i));
      if (plan != null) result.add(plan);
    }
    return result.toArray(new SectionSearchQueryPlan[result.size()]);
  }
}
